package top.dotgo.web.base.kit;


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.dotgo.kit.CfgKit;
import top.dotgo.kit.Const;
import top.dotgo.kit.ContextKit;
import top.dotgo.kit.ex.DotException;
import top.dotgo.kit.ex.DotMsg;
import top.dotgo.web.base.R;
import top.dotgo.web.base.bean.JwtBean;
import top.dotgo.web.base.service.AccInfoService;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author : lin
 * @date : 2019/10/10 11:37
 */
@Component
public class JwtKit {
    public static final String FROM = "from";
    public static final String TOKEN = "token";
    /**
     * 签名签发者
     */
    public static final String SERVICE = "SERVICE";
    public static final String USER_AGENT = "userAgent";
    public static final String ACC_ID = "accId";
    public static final String JTI = "jti";

    private static final ThreadLocal<JwtBean> LOCAL_JWT_BEAN = new ThreadLocal<>();

    private static AccInfoService accInfoService;

    /**
     * 获取一个新token
     *
     * @param accId     accId
     * @param from      from
     * @param userAgent userAgent
     * @return String
     * @deprecated 已失效
     */
    @Deprecated
    public static String getToken(String accId, String from, String userAgent) {
        Algorithm algorithm = Algorithm.HMAC256(CfgKit.getJwtSalt());

        // 头部信息
        Map<String, Object> map = new HashMap<>(5);
        map.put("alg", "HS256");
        map.put("typ", "JWT");
        Date nowDate = DateUtil.date();
        Date expireDate = DateUtil.offsetSecond(nowDate, CfgKit.getJwtExpire());


        return JWT.create()
                // 设置头部信息 Header
                .withHeader(map)
                //设置 载荷 签名是有谁生成 例如 服务器
                .withIssuer(SERVICE)
                //设置 载荷 签名的主题
                .withSubject(TOKEN)
                //设置 载荷 定义在什么时间之前，该jwt都是不可用的.
                // .withNotBefore(new Date())
                //设置 载荷 签名的观众 也可以理解谁接受签名的
                .withAudience("ChGo")
                //设置 载荷 生成签名的时间
                .withIssuedAt(nowDate)
                //设置 载荷 签名过期的时间
                .withExpiresAt(expireDate)
                .withClaim(ACC_ID, accId)
                .withClaim(FROM, from)
                .withClaim(USER_AGENT, SecureUtil.md5(userAgent).substring(0, 5))
                //签名 Signature
                .sign(algorithm);
    }

    public static JwtBean getJwtBean(String token) {
        Algorithm algorithm = Algorithm.HMAC256(CfgKit.getJwtSalt());
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(SERVICE).build();
        JwtBean bean = new JwtBean();
        DecodedJWT jwt;
        try {
            jwt = verifier.verify(token);
        } catch (JWTDecodeException ex) {
            throw new DotException(ex.getMessage(), DotMsg.LOGIN_ERR);
        }
        if (jwt == null || jwt.getClaims() == null) {
            throw new DotException(DotMsg.LOGIN_ERR, "token is invalid ");
        }
        Map<String, Claim> claims = jwt.getClaims();
        if (claims == null || claims.size() <= 0) {
            throw new DotException(DotMsg.LOGIN_ERR, "claims is invalid ");
        }
        //验证 USER_AGENT
//        if (claims.get(USER_AGENT) != null && !claims.get(USER_AGENT).asString().equals(getUserAgentEnc(null))) {
//            //TODO 暂时不验证 userAgent
////            if (!CfgKit.isDebug()) {
////                throw new DotException(DotMsg.LOGIN_ERR, "userAgent is invalid ");
////            }
//        }
        bean.setUserAgent(claims.get(USER_AGENT).asString());

        //验证 jti
        if (claims.get(JTI) == null || StrUtil.isEmpty(claims.get(JTI).asString())) {
            throw new DotException(DotMsg.LOGIN_ERR, DotMsg.LOGIN_ERR_MSG);
        }
        bean.setJti(claims.get(JTI).asString());
        //设置 请求来源
        Const.from from;
        if (claims.get(FROM) != null && !StrUtil.isEmpty(claims.get(FROM).asString())) {
            from = Const.from.valueOf(claims.get(FROM).asString());
        } else {
            from = Const.from.BIZ;
        }
        bean.setFrom(from);
        ContextKit.setFrom(from);

        //验证 账号id
        if (claims.get(ACC_ID) == null || StrUtil.isEmpty(claims.get(ACC_ID).asString())) {
            throw new DotException(DotMsg.LOGIN_ERR, DotMsg.LOGIN_ERR_MSG);
        }
        bean.setAccId(Long.valueOf(claims.get(ACC_ID).asString()));
        ContextKit.setAccId(bean.getAccId());
        ContextKit.setToken(token);
        LOCAL_JWT_BEAN.set(bean);
        return bean;
    }

    /**
     * 验证token
     *
     * @param token token
     */
    public static R verify(String token) {
        JwtBean bean;
        if (LOCAL_JWT_BEAN.get() == null) {
            bean = getJwtBean(token);
        } else {
            bean = LOCAL_JWT_BEAN.get();
        }
        if (ContextKit.getAccInfo() == null) {
            R r = accInfoService.loadAccInfo(bean.getAccId(), bean.getJti());
            if (!r.getCode().equals(DotMsg.SUCCESS_CODE)) {
                return R.err(DotMsg.LOGIN_ERR, DotMsg.LOGIN_ERR_MSG);
            }
        }
        return R.ok();
    }

    /**
     * 获得 加密后的 userAgent
     *
     * @param userAgent userAgent
     * @return String
     */

    public static String getUserAgentEnc(String userAgent) {
        if (StrUtil.isEmpty(userAgent)) {
            HttpServletRequest request = (HttpServletRequest) ContextKit.getRequest();
            userAgent = request.getHeader("user-agent");
        }
        return SecureUtil.md5(userAgent).substring(0, 5);
    }

    /**
     * 清除线程变量
     */
    public static void remove() {
        LOCAL_JWT_BEAN.remove();
    }

    @Autowired
    public void setAccInfoService(AccInfoService accInfoService) {
        JwtKit.accInfoService = accInfoService;
    }

}
